﻿using PagedList;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Tippelde.Entities;
using Tippelde.Services.Dtos;
using Tippelde.Services.Exceptions;

namespace Tippelde.Services.Events
{
    public class EventManager : IEventManager
    {

        private readonly Expression<Func<Event, EventHeaderData>> EventHeaderSelector =
            e => new EventHeaderData
            {
                Id = e.Id,
                Name = e.Name,
                ShortName = e.ShortName,
                Status = e.Status,
                Description = e.EventDescriptor.Description,
                HasResult = (e.Result != null),

                Result = new ResultHeaderData
                {
                    AwayResult = e.Result != null ? e.Result.AwayResult : default(int),
                    HomeResult = e.Result != null ? e.Result.HomeResult : default(int),
                },
                HomeTeam = new TeamHeaderData
                {
                    Id = e.EventDescriptor.HomeTeam.Id,
                    LogoUrl = e.EventDescriptor.HomeTeam.LogoUrl,
                    Name = e.EventDescriptor.HomeTeam.Name,
                    ShortName = e.EventDescriptor.HomeTeam.ShortName,
                    Description = e.EventDescriptor.HomeTeam.Description,
                },
                AwayTeam =
                new TeamHeaderData
                {
                    Id = e.EventDescriptor.AwayTeam.Id,
                    LogoUrl = e.EventDescriptor.AwayTeam.LogoUrl,
                    Name = e.EventDescriptor.AwayTeam.Name,
                    ShortName = e.EventDescriptor.AwayTeam.ShortName,
                    Description = e.EventDescriptor.AwayTeam.Description,
                },
                EventDate = e.EventDate,
                BetStartAt = e.BetStartAt,
                BetEndAt = e.BetEndAt
            };

        private readonly Expression<Func<Event, EventWithBetData>> EventWithBetSelector =
            e => new EventWithBetData
                    {
                        Id = e.Id,
                        Name = e.Name,
                        ShortName = e.ShortName,
                        Status = e.Status,
                        Description = e.EventDescriptor.Description,
                        HasResult = (e.Result != null),

                        Result = new ResultHeaderData
                        {
                            AwayResult = e.Result != null ? e.Result.AwayResult : default(int),
                            HomeResult = e.Result != null ? e.Result.HomeResult : default(int),
                        },


                        HomeTeam = new TeamHeaderData
                        {
                            Id = e.EventDescriptor.HomeTeam.Id,
                            LogoUrl = e.EventDescriptor.HomeTeam.LogoUrl,
                            Name = e.EventDescriptor.HomeTeam.Name,
                            ShortName = e.EventDescriptor.HomeTeam.ShortName,
                            Description = e.EventDescriptor.HomeTeam.Description,
                        },
                        AwayTeam =
                        new TeamHeaderData
                        {
                            Id = e.EventDescriptor.AwayTeam.Id,
                            LogoUrl = e.EventDescriptor.AwayTeam.LogoUrl,
                            Name = e.EventDescriptor.AwayTeam.Name,
                            ShortName = e.EventDescriptor.AwayTeam.ShortName,
                            Description = e.EventDescriptor.AwayTeam.Description,
                        },
                        EventDate = e.EventDate,
                        BetStartAt = e.BetStartAt,
                        BetEndAt = e.BetEndAt
                    };

        public IPagedList<EventHeaderData> GetEventHeadersForUser(string userName, int? page, int? count)
        {
            using (var ctx = new TippeldeEntities())
            {
                return ctx.Events.Include(e => e.Groups).Where(e => e.Groups.SelectMany(g => g.UserGroups).Any(u => u.User.UserName == userName)).OrderByDescending(e => e.EventDate).Select(EventWithBetSelector).ToPagedList(page ?? 1, count ?? 5);
            }
        }

        public List<EventHeaderData> GetUpcomingEventHeadersForUser(string userName, int? count)
        {
            using (var ctx = new TippeldeEntities())
            {
                var events = ctx.Events.Include(e => e.Groups).Where(e => e.Groups.SelectMany(g => g.UserGroups).Any(u => u.User.UserName == userName)).OrderByDescending(e => e.EventDate);
                return events.Where(e=> e.EventDate>DateTime.Now && !e.Bets.Any(b=>b.User.UserName==userName)).OrderBy(e => e.EventDate).Take(5).Select(EventHeaderSelector).ToList();
            }
        }

        public EventWithBetData GetEventHeaderWithBet(string userName, int eventId)
        {
            using (var ctx = new TippeldeEntities())
            {
                var e = ctx.Events.Include(_e => _e.Groups).Where(_e => _e.Groups.SelectMany(g => g.UserGroups).Any(u => u.User.UserName == userName)).Select(EventWithBetSelector).Single(_e => _e.Id == eventId);
                var editor = ctx.Users.Single(u => u.UserName == userName);
                var betTime = DateTime.Now;
                Bet bet = ctx.Bets.Where(b => b.Event.Id == e.Id && b.User.UserName == userName).SingleOrDefault();
                e.HasBet = (bet != null);
                e.IsBetInterval = betTime.CompareTo(e.BetStartAt) > -1 && betTime.CompareTo(e.BetEndAt) < 1 && betTime.CompareTo(e.EventDate) < 1;
                if (bet != null)
                {
                    e.Bet = new BetHeaderData
                    {
                        AwayBet = bet.AwayBet,
                        HomeBet = bet.HomeBet,
                        BetAt = bet.BetAt,
                        Id = bet.Id,
                        Checked = bet.Checked,
                    };
                    try
                    {
                        e.BetResultStatus = (BetResultStatus?)bet.BetResult.PointsEarned;
                    }
                    catch (Exception) { }
                }
                try
                {
                    e.IsUserEditor = editor.UserGroups.Any(ug => ug.IsEditor && ug.Group.Events.Any(ee => ee.Id == e.Id));
                }
                catch (Exception) { }
                return e;
            }
        }


        public IPagedList<EventWithBetData> GetEventHeadersWithBetForUser(string userName, int? page)
        {
            using (var ctx = new TippeldeEntities())
            {
                var events = ctx.Events.Include(e => e.Groups).Where(e => e.Groups.SelectMany(g => g.UserGroups).Any(u => u.User.UserName == userName)).OrderByDescending(e => e.EventDate).Select(EventWithBetSelector).ToPagedList(page ?? 1, 10);
                var editor = ctx.Users.Single(u => u.UserName == userName);
                var betTime = DateTime.Now;
                foreach (var e in events)
                {
                    Bet bet = ctx.Bets.Where(b => b.Event.Id == e.Id && b.User.UserName == userName).SingleOrDefault();
                    e.HasBet = (bet != null);
                    e.IsBetInterval = betTime.CompareTo(e.BetStartAt) > -1 && betTime.CompareTo(e.BetEndAt) < 1 && betTime.CompareTo(e.EventDate) < 1;
                    if (bet != null)
                    {
                        e.Bet = new BetHeaderData
                        {
                            AwayBet = bet.AwayBet,
                            HomeBet = bet.HomeBet,
                            BetAt = bet.BetAt,
                            Id = bet.Id,
                            Checked = bet.Checked,
                        };
                        try
                        {
                            e.BetResultStatus = (BetResultStatus?)bet.BetResult.PointsEarned;
                        }
                        catch (Exception) { }
                    }
                    try
                    {
                        e.IsUserEditor = editor.UserGroups.Any(ug => ug.IsEditor && ug.Group.Events.Any(ee => ee.Id == e.Id));
                    }
                    catch (Exception) { }

                }
                return events;
            }
        }

        public IPagedList<EventHeaderData> GetEventHeaders(string userName, int groupId, int? page)
        {
            using (var ctx = new TippeldeEntities())
            {
                return ctx.Users.Where(u => u.UserName == userName).SelectMany(u => u.UserGroups).Where(g => g.GroupId == groupId).Select(ug => ug.Group).SelectMany(g => g.Events).OrderByDescending(e => e.EventDate).Select(EventWithBetSelector).ToPagedList(page ?? 1, 10);
            }
        }


        public IPagedList<EventWithBetData> GetEventHeadersWithBet(string userName, int groupId, int? page)
        {
            using (var ctx = new TippeldeEntities())
            {
                var events = ctx.Users.Where(u => u.UserName == userName).SelectMany(u => u.UserGroups).Where(g => g.GroupId == groupId).Select(ug => ug.Group).SelectMany(g => g.Events).OrderByDescending(e => e.EventDate).Select(EventWithBetSelector).ToPagedList(page ?? 1, 10);
                var editor = ctx.Users.Single(u => u.UserName == userName);
                var betTime = DateTime.Now;
                foreach (var e in events)
                {
                    Bet bet = ctx.Bets.Where(b => b.Event.Id == e.Id && b.User.UserName == userName).SingleOrDefault();
                    e.HasBet = (bet != null);
                    e.IsBetInterval = betTime.CompareTo(e.BetStartAt) > -1 && betTime.CompareTo(e.BetEndAt) < 1 && betTime.CompareTo(e.EventDate) < 1;
                    if (bet != null)
                    {
                        e.Bet = new BetHeaderData
                        {
                            AwayBet = bet.AwayBet,
                            HomeBet = bet.HomeBet,
                            BetAt = bet.BetAt,
                            Id = bet.Id,
                            Checked = bet.Checked,
                        };
                        try
                        {
                            e.BetResultStatus = (BetResultStatus?)bet.BetResult.PointsEarned;
                        }
                        catch (Exception) { }
                    }
                    try
                    {
                        e.IsUserEditor = editor.UserGroups.Any(ug => ug.IsEditor && ug.Group.Events.Any(ee => ee.Id == e.Id));
                    }
                    catch (Exception) { }

                }
                return events;
            }
        }

        public EventHeaderData GetEventHeader(int eventId)
        {
            using (var ctx = new TippeldeEntities())
            {
                return ctx.Events.Where(e => e.Id == eventId).Select(EventWithBetSelector).SingleOrDefault();
            }
        }


        public EventHeaderData EditEvent(EventHeaderData ehd, string creatorName, IEnumerable<int> groupIds)
        {
            using (var ctx = new TippeldeEntities())
            {
                Team awayTeam = ctx.Teams.SingleOrDefault(t => t.Id == ehd.AwayTeam.Id);
                Team homeTeam = ctx.Teams.SingleOrDefault(t => t.Id == ehd.HomeTeam.Id);
                User creator = ctx.Users.SingleOrDefault(u => u.UserName == creatorName);
                if (homeTeam == null)
                    throw new TippeldeException("No or wrong Home Team Id passed");
                if (awayTeam == null)
                    throw new TippeldeException("No or wrong Away Team Id passed");
                if (creator == null)
                    throw new TippeldeException("Creator not exists");
                Event e = ctx.Events.Single(e_ => e_.Id == ehd.Id);

                e.Name = ehd.Name;
                e.ShortName = ehd.ShortName;
                e.Creator = creator;
                e.EventDate = ehd.EventDate;
                e.BetStartAt = ehd.BetStartAt ?? DateTime.Now;
                e.BetEndAt = ehd.BetEndAt ?? ehd.EventDate.Subtract(TimeSpan.FromHours(2));
                e.Available = (ehd.BetStartAt ?? DateTime.Now) >= DateTime.Now && (ehd.BetEndAt ?? ehd.EventDate.Subtract(TimeSpan.FromHours(2))) <= DateTime.Now;
                e.Status = ehd.Status;

                EventDescriptor ed = e.EventDescriptor;
                ed.AwayTeam = awayTeam;
                ed.HomeTeam = homeTeam;
                ed.Description = ehd.Description;
                ed.Name = ehd.Name;
                ed.EventInfo = ehd.ShortName;

                foreach (var group in ctx.Groups.ToList())
                {
                    group.Events.Remove(e);
                }
                e.Groups = ctx.Groups.Where(g => groupIds.Contains(g.Id)).ToList();
                foreach (var group in e.Groups)
                {
                    group.Events.Add(e);
                }
                ctx.SaveChanges();
                return ehd;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ehd"></param>
        /// <exception cref="TippeldeException">Thrown on error</exception>
        /// <returns></returns>
        public EventHeaderData CreateEvent(EventHeaderData ehd, string creatorName, IEnumerable<int> groupIds)
        {
            using (var ctx = new TippeldeEntities())
            {
                Team awayTeam = ctx.Teams.SingleOrDefault(t => t.Id == ehd.AwayTeam.Id);
                Team homeTeam = ctx.Teams.SingleOrDefault(t => t.Id == ehd.HomeTeam.Id);
                User creator = ctx.Users.SingleOrDefault(u => u.UserName == creatorName);
                if (homeTeam == null)
                    throw new TippeldeException("No or wrong Home Team Id passed");
                if (awayTeam == null)
                    throw new TippeldeException("No or wrong Away Team Id passed");
                if (creator == null)
                    throw new TippeldeException("Creator not exists");
                Event e = new Event()
                {
                    Name = ehd.Name,
                    ShortName = ehd.ShortName,
                    Creator = creator,
                    EventDate = ehd.EventDate,
                    BetStartAt = ehd.BetStartAt ?? DateTime.Now,
                    BetEndAt = ehd.BetEndAt ?? ehd.EventDate.Subtract(TimeSpan.FromHours(2)),
                    Available = (ehd.BetStartAt ?? DateTime.Now) >= DateTime.Now && (ehd.BetEndAt ?? ehd.EventDate.Subtract(TimeSpan.FromHours(2))) <= DateTime.Now,
                    Status = ehd.Status
                };
                EventDescriptor ed = new EventDescriptor()
                {
                    AwayTeam = awayTeam,
                    HomeTeam = homeTeam,
                    Description = ehd.Description,
                    Name = ehd.Name,
                    EventInfo = ehd.ShortName,
                    Event = e,
                };
                e.EventDescriptor = ed;
                e.Groups = ctx.Groups.Where(g => groupIds.Contains(g.Id)).ToList();
                ctx.EventDescriptors.Add(ed);
                ctx.Events.Add(e);
                ctx.SaveChanges();
                ehd.Id = e.Id;
                return ehd;
            }
        }


        public void DeleteEvent(int eventId)
        {
            using(var ctx = new TippeldeEntities())
            {
                var _event = ctx.Events.Single(e => e.Id == eventId);
                ctx.EventDescriptors.Remove(_event.EventDescriptor);
                if (_event.Result != null)
                {
                    ctx.Results.Remove(_event.Result);
                }
                ctx.Bets.RemoveRange(_event.Bets);
                ctx.BetResults.RemoveRange(_event.BetResults);

                var groups = ctx.Groups.Where(g => g.Events.Any(e=>e.Id==_event.Id)).ToList();
                groups.ForEach(g => g.Events.Remove(_event));

                ctx.SaveChanges();
            }
        }
    }
}
